Ubuntu 16.10 配置无线网卡驱动的问题,同样适用14.04和16.04。针对的设备是Realtek RTL8111/8168 PCI Express Gigabit Ethernet Controller,表现为网络不稳定,经常掉线,且网速较慢,分析后认为是驱动不匹配。

Ubuntu自带的驱动为r8169,高于此类网卡,导致兼容性差,影响了网络连接效果。 解决方法如下,参考链接见文末。

1. Check driver and device info

查看系统为网卡型号和对应的驱动,利用如下指令

1
$ lspci -vv >> device.txt

打开device.txt,找到网卡设备,样例如下。其中驱动部分显示为r8169,与现有网卡不吻合

1
2
3
4
5
6
7
8
9
10
11
12
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 0b)
Subsystem: Acer Incorporated [ALI] RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B-DisINTx+
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0, Cache Line Size: 64 bytes
Interrupt: pin A routed to IRQ 27
Region 0: I/O ports at d000 [size=256]
Region 2: Memory at f7104000 (64-bit, prefetchable) [size=4K]
Region 4: Memory at f7100000 (64-bit, prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: r8169
Kernel modules: r8169

2. Download driver of r8168

Realtek官网下载rtl8168驱动, 需要根据Linux的内核版本进行选择,例如我的内核是4.9.1,则选择4.7以上的驱动。

更新[2019-07-28]:Realtek的官方链接貌似打不开了,而且在我的系统更新到18.04以及内核更新以后出现了奇怪的编译错误(如下),Google以后发现是因为内核4.15以后,setup_timer函数被更新为timer_setup,因此出现了编译错误的问题。参考建议,安装更新后的r8168.045.08-2驱动能够解决问题,该驱动的下载链接为这里

1
2
3
4
error: implicit declaration of function ‘setup_timer’; did you mean ‘sk_stop_timer’? [-Werror=implicit-function-declaration]
setup_timer(timer, rtl8168_esd_timer, (unsigned long)dev);
^~~~~~~~~~~
sk_stop_timer

3. Install driver

安装驱动,用如下指令,安装完成后要重启系统。

1
2
3
4
$ tar -xvf 0010-r8168-8.045.08.tar.bz2
$ cd r8168-8.045.08
$ sudo ./autorun.sh
$ sudo reboot

重启以后,理论上网络连接恢复正常。重新查看系统设备信息,有如下结果,驱动已经更新为r8168

1
2
3
4
5
6
7
8
9
10
11
12
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 0b)
Subsystem: Acer Incorporated [ALI] RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B-DisINTx+
Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
Latency: 0, Cache Line Size: 64 bytes
Interrupt: pin A routed to IRQ 27
Region 0: I/O ports at d000 [size=256]
Region 2: Memory at f7104000 (64-bit, prefetchable) [size=4K]
Region 4: Memory at f7100000 (64-bit, prefetchable) [size=16K]
Capabilities: <access denied>
Kernel driver in use: r8168
Kernel modules: r8168

Reference

[1] Ubuntu 16.04 RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller” 不能上网

Comment and share

在师兄的安利下入坑了emacs,一个传说中学习曲线比vim还陡的编辑器 (好像Vim更陡一点),目前的体会是小拇指疼。但是,上手以后就真的不想用其他的编辑器了 (小声BB我正在用markdown写博客)。集成了Vim使用习惯的spacemacs非常适合用来记笔记,如果熟悉latex语法的话,甚至能比手写效率高。写代码的话,还是gedit吧,除了缩进不好用,其实还行 (感觉要被码农口水淹死)。。。

这篇笔记简单记录一下spacemacs的安装流程 (Ubuntu 18.04和macOS),基于org-mode的语法和导出为pdf的流程,以及添加spacemacs对中文的支持。最后一点算是重点内容,谷歌了很久找到了相对容易理解的方法。

spacemacs的安装 (Ubuntu和macOS)

针对spacemacs的安装,参考其github上的README即可,我会简述一下在Ubuntu 18.04 (Linux) 和macOS上的安装和配置方式,目前都是默认配置。

Ubuntu 18.04

针对Ubuntu 18.04的spacemacs安装过程分为以下三步,即

  1. 下载emacs (要求版本高于emacs 24,Ubuntu14.04貌似不支持,需要独立下载并编译)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ sudo apt update
    $ sudo apt install emacs
    # 查看一下版本信息
    $ emacs --version
    GNU Emacs 25.2.2
    Copyright (C) 2017 Free Software Foundation, Inc.
    GNU Emacs comes with ABSOLUTELY NO WARRANTY.
    You may redistribute copies of GNU Emacs
    under the terms of the GNU General Public License.
    For more information about these matters, see the file named COPYING.
  2. 配置spacemacs
    参考spacemacs的default installation,首先克隆其repository到/home/xxx(这里的xxx是用户名)

    1
    $ git clone https://github.com/syl20bnr/spacemacs ~/.emacs.d
  3. 安装相关插件
    命令行输入emacs初始化spacemacs环境,完成安装。这一步具体安装什么还没太懂,后面慢慢研究一下。建议安装过程中选择默认的编辑工具为vim,个人感觉vim比较适合初学者,掌握几个基本命令就可以工作了。

macOS 10.14

针对macOS,我的系统是10.14 Mojave。因为用户习惯,我把macOS的terminal当linux用,所以这里的安装步骤与Ubuntu类似,仅在第一步有区别。即安装emacs时,采用homebrew工具,具体的命令行写法如下:

1
$ brew install emacs

除此之外,spacemacs官方也提供了安装教程,可以参考这里

org-mode的基本语法

介绍完spacemacs的安装,便可以开始记笔记了,这里采用的是org-mode,org除了提供将文本导出为html和pdf格式外,还可以制作TODO list和日历等,非常适合日常的工作。对于工科孩子,笔记或者小论文的内容主要包含文字、公式、图表、链接和参考文献等,简单举例说明一下他们的语法,详细内容可以参考这篇博客,翻译自org的官方文档,非常详细。我在这里提供了一个与本文内容相同的org笔记以及导出的pdf,感兴趣的话可以作为参考模板。效果如下图

注意:如果要导出pdf,需要预先安装tex套件,建议Texlive,在Ubuntu下可以之间用sudo apt install texlive-2016实现。

文档初始化

spacemacs通过文件的后缀名识别文档的类型,对于org-mode,文件的后缀需要为.org。可以采用如下方式初始化笔记并保存,

1
$ emacs notebook.org

添加笔记的标题和作者等信息

通过在文档头部添加相关的称为”In-buffer settings”的关键字及相关的内容,例如

1
2
3
#+TITLE: A notebook of spacemacs
#+AUTHOR: Pikachu
#+DATE: 2016-06-11

不同级别标题的基本语法

与markdown类似,org提供了由*引导的分级标题的语法规则,例如

1
2
3
4
* 一级标题
** 二级标题
*** 三级标题
普通文字

一旦该行设定为标题,spacemacs会对该标题所在区域进行自动折叠,通过TAB键可以实现段落的展开和折叠。

表格

同样的,与markdown类似,org支持添加表格,常用的表格样例如下,

1
2
3
4
| Name | Phone | Age |
|------ +-------+-----|
| Alice | 12345 | 20 |
| Bob | 23456 | 21 |

Name Phone Age
Alice 12345 20
Bob 23456 21

在表格的填写过程中,有几个快捷键非常好用,例如TAB键用于对齐和增加新的行,其他快捷键建议参考这里

公式和代码

在org-mode下插入公式等价于在latex文档中的操作,直接以equation环境或者$环境即可,例如

1
2
3
\begin{equation}
x = a + b
\end{equation}

插入代码则采用由#+引导的block,即

1
2
3
4
5
6
7
8
#+BEGIN_SRC <language>
code body
#+END_SRC
# For example
#+BEGIN_SRC python
print("Hello world!")
#+END_SRC

1
print("Hello world!")

超链接和脚注

org对于超链接的支持要优于markdown,能够支持网址、本地文件、文档内部超链接等功能,例如

1
2
3
4
5
6
# 网址
[[<网址>][<描述>]]
# 本地文件
[[./file.xxx]]
# 文档内部超链接
[[超链接和脚注]]

而脚注的添加方式有两种,即org-mode和基于latex语法的方式,二者在生成的pdf中效果相同。

1
2
3
4
5
6
# org-mode
引用方式: 这是一个脚注[fn:1],它的位置在本页下方。
脚注格式: [fn:1] 这是一个脚注。
# latex格式
这是一个脚注\footnote{这是一个脚注}

导出为pdf

最后,需要将记录的文档导出为pdf文件,导出方法是Ctrl+c-Ctrl+e-l-p

添加中文支持

受到系统语言设置的限制,spacemacs对于中文的支持存在一些小bug。当系统的语言设定为中文时,直接借助已经安装的拼音输入法便可以实现中文的输入。但是当系统语言为英文时,需要添加对中文的支持。

系统语言为英文时如何在spacemacs添加对中文输入法的支持

笔者的系统是Ubuntu18.04,系统语言为英文,安装了fcitx中文中文输入法,在spacemacs中添加中文支持的一种简单方法是在初始化spacemacs时添加如下语句,

1
LC_CTYPE=zh_CN.UTF-8 emacs

其中LC_CTYPE表示本地字符和字符串的编码格式,这里强制设定为中文UTF-8。如果笔记的内容主要为中文,可以在shell的配置文件中固定这一命令,即

1
alias emacszh="LC_CTYPE=zh_CN.UTF-8 emacs"

解决导出的PDF中文不显示的问题

在解决spacemacs对中文输入法的支持后,还需要将默认的latex编译命令由pdflatex修改为xelatex,因为前者不支持中文。参考emacs-china中的问题,可以通过如下方式修改编译指令,即在文档头部增加,

1
2
#+LATEX_HEADER: \usepackage{ctex}
#+LATEX_COMPILIER: xelatex

当然,针对这一问题的解决方案还挺多的,多数涉及到LISP的语法和修改spacemacs的配置文件,还是挺麻烦的。

References

[1] spacemacs
[2] spacemacs default installation
[3] Org-mode 简明手册
[4] Org-mode 添加中文支持
[5] Orgmode导出PDF显示不了中文

Comment and share

最近有个奇怪的需求,批量修改文件的访问和修改时间。原因是在windows下重命名的时候,批量修改了文件的名称。感觉跟说绕口令一样。针对这一需求,尝试利用python的timeos两个库来解决。

查看文件的历史时间

首先基于如下代码查看文件的创建修改最后访问时间,即

1
2
3
4
5
6
7
8
import time
import os
filepath = "./file.xxx"
# 查看文件时间戳
time_create = os.path.getctime(filepath)
time_modify = os.path.getmtime(filepath)
time_access = os.path.getatime(filepath)

若需要得到格式化的时间,可以利用time.ctime()函数获取,例如

1
2
3
print(time.ctime(time_create))
Mon Jun 13 07:34:19 2016

修改文件的创建和修改时间

由于恢复的文件名以后,文件的修改时间发生了变化,需要对其时间戳进行修改,此时采用os.utime()方法来实现,例如

1
2
3
new_access_time = xxx # 注意这里需要是整数
new_modify_time = xxx # 注意这里需要是整数
os.utime(filepath, (new_access_time, new_modify_time))

References

[1] python如何不改动文件的情况下修改文件的 修改日期

Comment and share

简单记录一下python读入tiff多通道图像的方法。尝试了scipy.misc.imreadcv2.imread等都没有成功,最后找到了用skimage读取的方法。

样例如下,我的图片是一个512x512x30的stack,

1
2
3
4
5
from skimage import io
img = io.imread(imgpath)
img.shape
(30, 512, 512)

Tip

在安装Opencv3的过程中,找到了一个比较简单的方法。。。也可能是已经进入python的库了吧。

1
pip3 install opencv-python

以前为什么那么麻烦。。。

Reference

Comment and share

简单记录一下matplotlib显示中文乱码的问题,参考了这篇博客。解决方法有两种,其中方法一成功了,第二种还是有点问题。。。

方法一

首先,查看已经安装的中文字体,如下

1
2
3
4
5
6
7
8
$ fc-list :lang=zh
/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc: Noto Serif CJK TC:style=Regular
/usr/share/fonts/WinFonts/STXINGKA.TTF: STXingkai,华文行楷:style=Regular
/usr/share/fonts/WinFonts/STCAIYUN.TTF: STCaiyun,华文彩云:style=Regular
/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc: Noto Serif CJK JP:style=Regular
/usr/share/fonts/opentype/noto/NotoSerifCJK-Regular.ttc: Noto Serif CJK KR:style=Regular
/usr/share/fonts/X11/misc/wenquanyi_10pt.pcf: WenQuanYi Bitmap Song:style=Regular
/usr/share/fonts/WinFonts/msjhl.ttc: Microsoft JhengHei,微軟正黑體,微軟正黑體 Light,Microsoft JhengHei Light:style=Light,Regular

这里我从Windows下复制了一些字体过来,安装方法比较简答,可以去Google一下。。。

查看字体以后,便可以通过设置FongProperties来实例化用于pyplot显示的字体,这里我选择了微软雅黑。后续在应用时,给出现中文的方法提供fontproperties值,便可以正常显示中文了。当然,其他的罗马字母的字体也会被修改。

1
2
3
4
from matplotlib.font_manager import *
myfont = FontProperties(fname='/usr/share/fonts/WinFonts/msyhbd.ttc')
...
plt.title("中文",fontproperties=myfont)

方法二

第二种方法是将中文字体添加到matplotlib的font中,根据安装位置的不同,可能出现在如下两个位置

  1. /usr/share/matplotlib/mpl-data/fonts
  2. ~/.local/lib/python3.6/site-packages/matplotlib/mpl-data/fonts

将字体文件复制到该路径下,并将字体名添加到matplotlibrc文件的sans-serif行,如下所示

1
#font.sans-serif: Microsoft YaHei, DejaVu Sans, Bitstream Vera Sans, Computer Modern Sans Serif, Lucida Grande, Verdana, Geneva, Lucid, Arial, Helvetica, Avant Garde, sans-serif

最后,在应用时,设定rcParams[‘font.sans-serif’]为Microsoft Yahei

1
2
plt.rcParams['font.sans-serif'] = ['Microsoft Yahei']
plt.rcParams['font.family']='sans-serif'

References

Comment and share

记录一下对AdaBoost的理解,参考了李航大大的《统计学习方法》。

Boost通过改变训练样本的权重,学习多个分类器,并将这些分类器进行线性组合,提高分类器性能。
提升方法是从弱学习算法出发,反复学习,得到一系列弱分类器 (又称为基本分类器),然后组合这些弱分类器,构成一个强分类器。

大多数的提升方法都是改变训练数据的概率分布 (训练数据的权值分布)。

  1. 每一轮训练时如何改变训练数据的权值活概率分布;
  2. 如何将弱分类器组合成一个强分类器。

针对问题1,AdaBoost通过提高前一轮弱分类器错误分类样本的权值,来提升此类样本的关注程度;针对问题2,AdaBoost采用加权多数表决的方法,即加大分类误差小的弱分类器的权值,减小分类误差大的弱分类器的权值。

Comment and share

最近在尝试配置网站的https,师兄推荐了Caddy来替换nginx,作为web server。Caddy采用golang语言编写,很容易安装,而且能自动帮助用户注册用于https的证书。

关于Caddy的安装和配置,这篇博客做了非常详细的介绍。我会按照我的配置重新复述一下,并且重点强调配置caddy.service服务时要注意的权限问题。

Caddy 安装

Caddy官网提供了自定义安装组件的方式,用户可以通过定制platform,plugins,telemetrylicense来生成caddy安装文件并下载。这里不建议采用curl -s https://getcaddy.com | bash脚本方式安装,因为会提示用户没有选择license

下载完成后,采用如下方式安装到系统的/usr/local/bin目录下,

1
2
3
4
$ mkdir caddy
$ cd caddy
$ tar -xvf ../caddy_v0.10.12_linux_amd64_custom_personal.tar.gz
$ sudo cp caddy /usr/local/bin/

Caddyfile

与nginx类似,Caddy也需要采用配置文件的方式读取server的信息,这里默认的配置文件名为Caddyfile。针对单host和多host,会有细微的区别,下面我分别列出配置文件的写法,

Single host
1
2
3
4
5
6
7
8
9
10
11
xxx.com
gzip
log /var/log/caddy/xxx.log
root /home/wwwroot/xxx/
# PHP backend
fastcgi / 127.0.0.1:2345 php {
index index.php index.html
}
rewrite {
to {path} {path}/ /index.php?{query}
}
Multiple hosts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
xxx.com {
gzip
log /var/log/caddy/xxx.log
root /home/wwwroot/xxx/
# PHP backend
fastcgi / 127.0.0.1:2345 php {
index index.php index.html
}
rewrite {
to {path} {path}/ /index.php?{query}
}}
yyy.com {
gzip
log /var/log/caddy/xxx.log
root /home/wwwroot/yyy/
# PHP backend
fastcgi / 127.0.0.1:2345 php {
index index.php index.html
}
rewrite {
to {path} {path}/ /index.php?{query}
}}

可以看出,如果要配置多个hosts,在每一个host的名称后用{}包含配置信息即可,其目的是让Caddy能区分不同的host.

配置candy.service

通常我们会将web server配置成系统的服务程序,运行在后台,Caddy也提供了相应的caddy.service的脚本,其下载和添加方法为,

1
sudo curl -s https://raw.githubusercontent.com/mholt/caddy/master/dist/init/linux-systemd/caddy.service -o /etc/systemd/system/caddy.service

打开caddy.service,其存储路径为/etc/systemd/system/,其中有几个部分需要注意和修改,例如执行该服务的用户及用户组,Caddyfile配置文件的路径,以及ssl证书的存储路径等。如果没有配置好,caddy服务会无法正常运行。

1
2
3
4
5
6
7
8
9
10
; User and group the process will run as.
User=root # 原文为 www-data
Group=root # 原文为 www-data
; Letsencrypt-issued certificates will be written to this directory.
Environment=CADDYPATH=/etc/ssl/caddy
; Always set "-root" to something safe in case it gets forgotten in the Caddyfile.
ExecStart=/usr/local/bin/caddy -log stdout -agree=true -conf=/etc/caddy/Caddyfile -root=/var/tmp
ExecReload=/bin/kill -USR1 $MAINPID

根据caddy.service文件的要求,我们需要新建一系列的文件夹,用于存放caddy生成的文件。

1
2
3
$ sudo mkdir /etc/caddy
$ sudo cp <path of the Caddyfile> /etc/caddy/Caddyfile
$ sudo mkdir /etc/ssl/caddy

完成以上操作以后,即可运行并查看Caddy的运行状态,运行前要记得关闭nginx服务,否则会提示80端口被占用。

1
2
3
$ sudo systemd daemon-reload # 重新加载systemd
$ sudo systemd enable caddy.service
$ sudo systemd status caddy.service # 查看caddy运行状态

测试

最后,打开浏览器,输入https://xxx.com,便可以查看是否配置成功。

References

Comment and share

今天讨论一下变分自动编码器 (Variational Auto-Encoder, VAE)及添加了条件约束的conditional VAE,包括其与传统AE的区别、推导思路和基于TensorFlow的实现。

传统的自动编码器主要用于特征提取、降维和压缩,是一种无监督的机器学习工具。其由编码器 (encoder)和解码器 (decoder)组成。编码器类似于分类问题中的特征提取模块,对样本进行特征提取并压缩成一个长度很小的向量;解码器则对这一特征向量进行解码以恢复出原始样本信息。通常衡量一个自动编码器好坏的指标 (或目标函数) 为最小均方误差 (mean squared error, MSE),

其中表示编码器的输入和解码器的输出。

传统AE的问题在于,解码器只能根据编码器在样本上提取的特征向量来输出恢复的样本,很难直接通过生成特征向量产生新的样本 (当然特征是可以通过类似Monte Carlo模拟或者GMM等模型来生成的,感兴趣可以看我的这个repo) ,所以传统的AE不完全是生成模型。

为了解决这一问题,Kingma & Welling 在Auto-Encoding Variational Bayes文中提出了变分自动编码器的概念。虽然与AE的组成类似,但二者是完全不同的,VAE是基于Bayes理论的,利用编解码器来抽象参数的非线性,最大化解码器输出相对于输入样本的似然性。 简而言之,就是试图获取数据本身的分布,从这个分布产生新的样本。

Variational auto-encoder

VAE首先定义一个称为latent variable的高维随机变量 (z的维数远小于),其分布为。然后定义映射,即由参数约束的映射在服从分布的的作用下能够近似真实样本 (如公式2,其中表示的概率分布)。通过便可以刻画样本。而由于是无法直接观测的,所以称为隐变量。

VAE假设~的标准正态分布,通过对参数进行估计,最大化公式(2)。因此,VAE网络实际上是用来求解这个分布的,与传统意义上的AE的理解是不同的。正如Tutorial on Variational Autoencoder里提到的,They are called “autoencoders” only because the final training objective that derives from this setup does have an encoder and a decoder, and resembles a traditional autoencoder.

VAE的Encoder可以理解为,而Decoder理解为,其目标便是最大化Decoder的输出或者. 由于是无法观测的,分布无法直接求解,VAE巧妙地利用了变分的方法,定义分布来近似。最终有为Encoder,而表示Decoder。

通常用KL divergence来描述两个分布之间的相似程度,KL散度越接近于零,二者越相似。定义,

利用Bayes定理,,推导得到 (具体过程参考文献2),

因为有,所以上式定义了的下界,即

显然,公式(3)的右边两项分别对应VAE中Decoder和Encoder的损失函数,也即要最大化的目标,这与GAN网络的思路非常类似。 通常可以用最小均方误差定义,也可以用交叉熵来描述。这里详细推导一下,以帮助其在TensorFlow下的实现。这里假设服从正态分布, 是服从标准正态分布的。

Conditional Variational auto-encoder

VAE只能生成服从样本分布的模拟,而对于MNIST这类样本有明确的标签的情形,能否通过设定某种条件,让VAE生成指定类别的手写体图像呢?基于这个motivation,人们提出了conditional VAE。假定样本的标签为,以作为Encoder和Decoder额外的约束 (如图1(c)),即可实现以上要求。相应的,条件变分自动编码器的似然函数变为,

Conditional VAE的约束与VAE基本相同,依然可以分为针对Encoder和Decoder的两个部分。

VAE和Conditional的TensorFlow实现

简单介绍一下VAE的TensorFlow实现,具体的Notebook参考这里

  • Init

    1
    2
    3
    4
    5
    batch_size = 64
    X_in = tf.placeholder(dtype=tf.float32, shape=[None, 784], name='X_in')
    X_out = tf.placeholder(dtype=tf.float32, shape=[None, 784], name='X_out')
    keep_prob = tf.placeholder(dtype=tf.float32, shape=(), name='keep_prob')
    n_latent = 32
  • Encoder

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def encoder(X_in, keep_prob):
    with tf.variable_scope("encoder", reuse=None):
    x = tf.layers.dense(X_in, units=128, activation=tf.nn.relu)
    x = tf.nn.dropout(x, keep_prob)
    x = tf.layers.dense(x, units=64, activation=tf.nn.relu)
    x = tf.nn.dropout(x, keep_prob)
    # The latent layer
    mu = tf.layers.dense(x, units=n_latent)
    sigma = 1e-6 + tf.nn.softplus(tf.layers.dense(x, units=n_latent)) # softplus to avoid negative sigma
    # reparameterization
    epsilon = tf.random_normal(tf.shape(mu))
    z = mu + tf.multiply(epsilon, sigma)
    return z, mu, sigma
  • Decoder

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def decoder(z, keep_prob):
    with tf.variable_scope("decoder", reuse=None):
    x = tf.layers.dense(z, units=64, activation=tf.nn.relu)
    x = tf.nn.dropout(x, keep_prob)
    x = tf.layers.dense(x, units=128, activation=tf.nn.relu)
    x = tf.nn.dropout(x, keep_prob)
    x = tf.layers.dense(x, units=784, activation=tf.nn.sigmoid)
    # sigmoid to contrain the output to [0,1)
    return x
  • Network instance

    1
    2
    z, mu, sigma = encoder(X_in, keep_prob)
    dec = decoder(z, keep_prob)
  • Loss function

    1
    2
    3
    4
    5
    6
    7
    loss_d = - tf.reduce_sum(
    X_out * tf.log(1e-8+dec) + (1.0 - X_out) * tf.log(1e-8+ 1.0 - dec), 1)
    loss_e = 0.5 * tf.reduce_sum(
    tf.square(mu) + tf.square(sigma) - tf.log(1e-8 + tf.square(sigma)) - 1, 1)
    # 注意这里的符号。。。
    loss = tf.reduce_mean(loss_d + loss_e)
    optimizer = tf.train.AdamOptimizer(0.0001).minimize(loss)

的不同定义及结果对比

最后贴一个结果,我对比了一下采用交叉熵和均方误差两种损失函数表述的结果,在参数相同的情况下,看起来MSE效果好一些,如下图。

References

Comment and share

本来想讨论InfoGAN的,先留个坑吧。今天先讨论标准的GAN,即生成对抗网络。GAN最先由Ian Goodfellow在2014年提出,跟Variational Auto-Encoder (VAE)的时间差不多,二者都是非常好的生成网络,在无监督学习中发挥了重要的作用。

Basic theory

生成对抗网络属于一个minmax game,其目标是Learn a generator, whose distribution matches the real data distribution ,即实现一个生成器用于从随机分布 (一般为高斯) 的噪声中生成与目标样本类似的模拟$x_g$。

为了衡量的相似性,设计一个称为Discriminator的对抗网络,该网络的输入可以是真实的,也可以是有generator生成的伪造的样本。我们要求能够分辨出输入给它的样本的真实性。因此discriminator的输出应该是样本真实的概率,

可以看出,当接近时,,即discriminator无法判断其输入来自真实样本还是伪造的样本。因此构成了一对相互对抗的网络,即生成对抗。相应的目标函数为,

参考Goodfellow的论文,该目标函数的优化求解分为两步,

Step1

  • 随机生成minibatch个z noise作为generator的输入,得到对应的输出
  • 随机选取minibatch个real data样本
  • 计算
  • 沿梯度方向传递给Discriminator的参数,进行参数学习。

Step2

  • 随机生成minibatch个z noise作为generator的输入,得到对应的输出
  • 计算
  • 沿梯度方向传递给Generator的参数,进行参数学习。

交替重复以上两步,直到收敛,即 After several steps of training, if G and D have enough capacity, they will reach a point at which both cannot improve.

Conditional case

以上是最原始的GAN,是一种无监督的网络。那么,以MNIST手写体数据库为例,如果我们想得到一个能够生成特定数字的生成器,应该如何做? 这一问题可以理解为GAN的有监督学习,即conditional GAN。这里插一句,InfoGAN可以在无标签的情况下通过将$z$分解为noise+latent两部分,无监督地学到具有样本的context semantic representations.绝对秒杀原始的GAN。。。

条件GAN的思路是什么呢?如上图所示,从左往右分别是原始的GAN、条件GAN两个网络。conditional GAN在generator和discriminator的输入部分均添加了一个新的变量$y$,即样本的标签,作为一种固定的约束,指导网络学习到样本内部的区别。但是网络的目标函数和训练过程的变化很小。 这个思路,和conditional variational auto-encoder非常相似。更新后的目标函数如下所示,

GAN的TensorFlow实现

首先吐个槽,GAN的调参真的不是一般的复杂,我发现Generator和Discriminator互搏的时候,经常训着训着,loss就偏了,最后的输出也很诡异。然后是激活函数的选择、dropout的keep_prob,以及z的长度,batch的大小都会有影响,我提供了GANConditional GAN的notebook,感兴趣可以自己去试试看。。。下面我分步骤介绍一下Conditional GAN的实现。

  • Initialization

    1
    2
    3
    4
    5
    6
    batch_size = 64
    z_len = 100
    z_noise = tf.placeholder(dtype=tf.float32, shape=[None, z_len], name='z_noise')
    y = tf.placeholder(dtype=tf.float32, shape=[None, 10], name='y')
    x_data = tf.placeholder(dtype=tf.float32, shape=[None, 784], name='x_data')
    keep_prob = tf.placeholder(dtype=tf.float32, shape=(), name='keep_prob')
  • Generator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def generator(z_noise, y, keep_prob, namescope='generator'):
    """The generator"""
    with tf.name_scope(namescope):
    net = tf.concat([z_noise, y], axis=1)
    net = tf.layers.dense(net, units=150, activation=tf.nn.relu, name='g_fc1')
    net = tf.nn.dropout(net, keep_prob=keep_prob)
    net = tf.layers.dense(net, units=300, activation=tf.nn.relu, name='g_fc2')
    net = tf.nn.dropout(net, keep_prob=keep_prob)
    net = tf.layers.dense(net, units=784, activation=tf.nn.sigmoid, name='g_fc3')
    return net
  • Discriminator

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def discriminator(d_in, y, z_len, keep_prob, namescope='discriminator', reuse=True):
    """The discriminator"""
    with tf.name_scope(namescope):
    net = tf.concat([d_in, y], axis=1)
    net = tf.layers.dense(net, units=300, activation=tf.nn.relu, name='d_fc1', reuse=reuse)
    net = tf.nn.dropout(net, keep_prob=keep_prob)
    net = tf.layers.dense(net, units=150, activation=tf.nn.relu, name='d_fc2', reuse=reuse)
    net = tf.nn.dropout(net, keep_prob=keep_prob)
    net = tf.layers.dense(net, units=1, activation=tf.nn.sigmoid, name='d_fc4', reuse=reuse)
    return net
  • Network instance

    1
    2
    3
    4
    # generate the network
    x_g = generator(z_noise, y, keep_prob)
    d_g = discriminator(x_g, y, z_len, keep_prob, reuse=False)
    d_data = discriminator(x_data, y, z_len, keep_prob)
  • Loss and optimizer

    1
    2
    3
    4
    5
    6
    7
    8
    # get variables
    varlist = tf.trainable_variables() # 查看待训练的参数,为了获取G和D两个网络的参数列表
    # The objective
    with tf.name_scope("loss"):
    loss_d = - (tf.reduce_mean(tf.log(1e-8 + d_data)) + tf.reduce_mean(tf.log(1e-8 + 1 - d_g)))
    loss_g = - tf.reduce_mean(tf.log(1e-8 + d_g))
    train_op_g = tf.train.AdamOptimizer(0.0001).minimize(loss_g, var_list=varlist[0:6])
    train_op_d = tf.train.AdamOptimizer(0.0001).minimize(loss_d, var_list=varlist[6:])

Conditional GAN在MNIST上的测试结果

针对MNIST手写体数据库,实现了一个可以根据标签生成指定数字的Conditional GAN,网络的配置如下表,参考了这篇博客

Subnet Layer Nodes Activation Dropout
Generator input [z, y] 32+10 —- —-
Generator FC 150 relu T
Generator FC 300 relu T
Generator FC 784 sigmoid F
Discriminator input 784+10 —- —-
Discriminator FC 300 relu T
Discriminator FC 150 relu T
Discriminator FC 1 sigmoid F

下面贴一下实验结果 (生成的手写体图像,每行对应一个数字),可以看出,随着迭代次数的增加,生成的数字越来约清晰,且准确性在提升。

References

Comment and share

TensorFlow的tf.layers和ctf.contrib.layers都提供了相关用于搭建神经网络的模块,但同一版本额和不同版本之间均存在区别,TF的更新真的是好快。。。

今天主要讨论一下tf.layers.densetf.contrib.layers.fully_connected的区别,二者都可以用于构建全连接层。参考tensorflow的文档,二者的参数如下,

  • tf.layers.dense

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    tf.layers.dense(
    inputs,
    units,
    activation=None,
    use_bias=True,
    kernel_initializer=None,
    bias_initializer=tf.zeros_initializer(),
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    trainable=True,
    name=None,
    reuse=None
    )
  • tf.contrib.layers.fully-connected

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    fully_connected(
    inputs,
    num_outputs,
    activation_fn=tf.nn.relu,
    normalizer_fn=None,
    normalizer_params=None,
    weights_initializer=initializers.xavier_initializer(),
    weights_regularizer=None,
    biases_initializer=tf.zeros_initializer(),
    biases_regularizer=None,
    reuse=None,
    variables_collections=None,
    outputs_collections=None,
    trainable=True,
    scope=None
    )

可以看出,tf.layers.dense相对更简单,没有提供默认的activationkernel_initializer, 而后者这两个参数都做了默认的初始化。使用时一定要显示说明这些,否则会出现不可控的错误。。。

Reference

Comment and share

Author's picture

Jason Ma

We are in the same story.


Astronomer? Software engineer


Shanghai